package com.luych.toolbox.docker.service.sdk.image;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luych.toolbox.docker.service.sdk.image.model.*;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;

public class DockerImages {

    private final String url;

    public DockerImages(String url) {
        this.url = url;
    }

    public DockerImage image(String id) {
        return new DockerImage(url, id);
    }

    public DockerImagesResult all() {
        final String uri = url + "/images/json?all={1}";
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(uri, DockerImagesResult.class, false);
    }

    public DockerImagesBuildResult build(DockerImagesBuildParam param) {
        String uri = url + "/build?dockerfile={dockerfile}&t={t}";
        Map<String, String> uriParam = new HashMap<>();
        uriParam.put("dockerfile", param.getDockerfile());
        uriParam.put("t", param.getT());
        if (!CollectionUtils.isEmpty(param.getBuildArgs())) {
            String buildArgsString;
            try {
                buildArgsString = new ObjectMapper().writeValueAsString(param.getBuildArgs());
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Convert buildArgs to json error", e);
            }
            uri += "&buildargs={buildargs}";
            uriParam.put("buildargs", buildArgsString);
        }
        RestTemplate restTemplate = new RestTemplate();
        InputStreamResource inputStreamResource = new InputStreamResource(param.getInputStream());
        HttpEntity<InputStreamResource> httpEntity;
        if (CollectionUtils.isEmpty(param.getRegistryConfig())) {
            httpEntity = new HttpEntity<>(inputStreamResource);
        } else {
            String registryConfigValue;
            try {
                registryConfigValue = new ObjectMapper().writeValueAsString(param.getRegistryConfig());
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Convert registryConfig to json error", e);
            }
            String registryConfigBase64 = Base64Utils.encodeToString(registryConfigValue.getBytes(StandardCharsets.UTF_8));
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add("X-Registry-Config", registryConfigBase64);
            httpEntity = new HttpEntity<>(inputStreamResource, httpHeaders);
        }
        RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity);
        ResponseExtractor<DockerImagesBuildResult> responseExtractor = response -> {
            InputStream inputStream = response.getBody();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line;
            List<String> lines = new ArrayList<>();
            String id = null;
            while ((line = bufferedReader.readLine()) != null) {
                if (param.getLineConsume() != null) {
                    param.getLineConsume().accept(line);
                }
                JsonNode jsonNode = new ObjectMapper().readTree(line);
                if (jsonNode.isObject()) {
                    Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
                    while (fields.hasNext()) {
                        Map.Entry<String, JsonNode> field = fields.next();
                        if (field.getKey().equals("aux")) {
                            id = field.getValue().get("ID").asText();
                        } else {
                            lines.add(field.getValue().asText());
                        }
                    }
                } else {
                    lines.add(line);
                }
            }
            return new DockerImagesBuildResult(id, lines);
        };
        return restTemplate.execute(uri, HttpMethod.POST, requestCallback, responseExtractor, uriParam);
    }

    public List<DockerImagesPullResult> pull(String name) {
        return this.pull(name, null, null, null);
    }

    public List<DockerImagesPullResult> pull(String name, Consumer<DockerImagesPullResult> consumer) {
        return this.pull(name, consumer, null, null);
    }

    public List<DockerImagesPullResult> pull(String name, Consumer<DockerImagesPullResult> consumer, String username, String password) {
        final String uri = url + "/images/create?fromImage={1}";
        HttpEntity<?> httpEntity;
        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
            Map<String, Object> userPass = new HashMap<String, Object>(){{
                this.put("username", username);
                this.put("password", password);
            }};
            String userPassString;
            try {
                userPassString = new ObjectMapper().writeValueAsString(userPass);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Convert registryConfig to json error", e);
            }
            userPassString = Base64Utils.encodeToString(userPassString.getBytes(StandardCharsets.UTF_8));
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.set("X-Registry-Auth", userPassString);
            httpEntity = new HttpEntity<>(null, httpHeaders);
        } else {
            httpEntity = new HttpEntity<>(null);
        }
        RestTemplate restTemplate = new RestTemplate();
        RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity);
        ResponseExtractor<List<DockerImagesPullResult>> responseExtractor = response -> {
            InputStream inputStream = response.getBody();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line;
            List<DockerImagesPullResult> pullResults = new ArrayList<>();
            while ((line = bufferedReader.readLine()) != null) {
                DockerImagesPullResult pullResult = new ObjectMapper().readValue(line, DockerImagesPullResult.class);
                if (consumer != null) {
                    consumer.accept(pullResult);
                }
                pullResults.add(pullResult);
            }
            return pullResults;
        };
        return restTemplate.execute(uri, HttpMethod.POST, requestCallback, responseExtractor, name);
    }

    public DockerImagesSearchResult search(String keyword) {
        return this.search(keyword, null);
    }

    public DockerImagesSearchResult search(String keyword, Long limit) {
        String uri = url + "/images/search?term={term}";
        Map<String, Object> uriParam = new HashMap<>();
        uriParam.put("term", keyword);
        if (limit != null) {
            uri += "&limit={limit}";
            uriParam.put("limit", limit);
        }
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(uri, DockerImagesSearchResult.class, uriParam);
    }

    public DockerImagesPruneResult prune() {
        final String uri = url + "/images/prune?filters={1}";
        Map<String, Object> filters = new HashMap<String, Object>(){{
            this.put("dangling", Collections.singletonList(Boolean.FALSE.toString()));
        }};
        String filtersString;
        try {
            filtersString = new ObjectMapper().writeValueAsString(filters);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Convert filters to json error", e);
        }
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.postForObject(uri, null, DockerImagesPruneResult.class, filtersString);
    }

    public DockerImagesLoadResult load(InputStream inputStream) {
        final String uri = url + "/images/load";
        InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
        HttpEntity<InputStreamResource> httpEntity = new HttpEntity<>(inputStreamResource);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<DockerImagesLoadResult> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, DockerImagesLoadResult.class);
        return responseEntity.getBody();
    }

    public DockerImagesLoadResult load(File file) {
        FileInputStream fileInputStream;
        try {
            fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Read stream from file error", e);
        }
        return this.load(fileInputStream);
    }
}
